﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Imaging;
using System.Security.Permissions;
using System.Diagnostics;
using System.IO;
using System.Windows.Media;
using System.Drawing;
using System.Net;
using Player.Core;

namespace Player
{
    public class FrameBase
    {
        public DateTime SystemTime { get; set; }
        public long TimeStamp { get; set; }
        public int Type { get; set; }//i-f p-f
        public virtual int FrameSize { get { return 0; } }
    }

    public class Frame : FrameBase
    {
        int m_bpp; // bytes per pixel in raw mode
        byte[] m_data;
        bool m_compressed;

        const int QLZ_COMPRESSION_LEVEL = 10;
        static int m_compressionLevel;

        public override int FrameSize
        {
            get
            {
                if (m_data == null)
                {
                    return 0;
                }
                else
                {
                    return m_data.Length;
                }
            }
        }

        static Frame()
        {
            m_compressionLevel = Settings.CompressionLevel;
        }

        public Frame(byte[] data, bool compressed, int bytesPerPixel)
        {
            m_bpp = bytesPerPixel;
            m_compressed = compressed;
            m_data = data;
        }

        public List<RfbRect> Rectangles(bool useCachedBuffer)
        {
            List<RfbRect> rectangles = new List<RfbRect>();

            // Decompress (if necessary) our data
            byte[] raw = DecompressFrameToCachedBuffer();

            using (MemoryStream ms = new MemoryStream(raw))
            {
                using (BinaryReader br = new BinaryReader(ms))
                {
                    int rectangleCount = br.ReadInt32();
                    for (int x = 0; x < rectangleCount; x++)
                    {
                        RfbRect rectangle = CreateRectFromStream(br, useCachedBuffer);
                        rectangles.Add(rectangle);
                    }
                }
            }

            return rectangles;
        }

        private byte[] DecompressFrameToCachedBuffer()
        {
            if (m_compressed)
            {
                if (m_compressionLevel == QLZ_COMPRESSION_LEVEL)
                {
                    QuickLZ qlz = new QuickLZ();
                    return qlz.Decompress(m_data);
                }
                else
                {
                    int decompressedSize = MemoryZip.DecompressedSize(m_data);
                    byte[] buffer = CachedBufferThreadCollection.Get(decompressedSize);
                    MemoryZip.Decompress(m_data, buffer);
                    return buffer;
                }
            }

            return m_data;
        }

        private RfbRect CreateRectFromStream(BinaryReader reader, bool useCachedBuffer)
        {
            CaptureContract.StoredEncoding rtype = (CaptureContract.StoredEncoding)reader.ReadInt32();
            RfbRect rect = null;
            switch (rtype)
            {
                case CaptureContract.StoredEncoding.Raw:
                    rect = new RfbRectRaw() { StoreEncoding = rtype };
                    break;
                case CaptureContract.StoredEncoding.CopyRect:
                    rect = new RfbRectCopyRect() { StoreEncoding = rtype };
                    break;
                case CaptureContract.StoredEncoding.DesktopResize:
                    rect = new RfbRectScreenResize() { StoreEncoding = rtype };
                    break;
                default:
                    Debug.Assert(false);
                    return null;
            }

            int x = reader.ReadInt32();
            int y = reader.ReadInt32();
            int w = reader.ReadInt32();
            int h = reader.ReadInt32();

            rect.Location = new Point() { X = x, Y = y };
            rect.Size = new Size() { Width = w, Height = h };

            // Per-type data
            switch (rtype)
            {
                case CaptureContract.StoredEncoding.CopyRect:
                    FillCopyRectFromStream(rect as RfbRectCopyRect, reader);
                    break;
                case CaptureContract.StoredEncoding.Raw:
                    FillRawRectFromStream(rect as RfbRectRaw, reader, useCachedBuffer);
                    break;
            }

            return rect;
        }

        private void FillCopyRectFromStream(RfbRectCopyRect rect, BinaryReader reader)
        {
            int x = IPAddress.NetworkToHostOrder((Int16)reader.ReadInt16());
            int y = IPAddress.NetworkToHostOrder((Int16)reader.ReadInt16());
            rect.Source = new Point() { X = x, Y = y };
        }

        private void FillRawRectFromStream(RfbRectRaw rect, BinaryReader reader, bool useCachedBuffer)
        {
            int size = rect.Size.Height * rect.Size.Width * m_bpp;
            if (useCachedBuffer)
                rect.Pixels = CachedBufferThreadCollection.Get(size);
            else
                rect.Pixels = new byte[size];

            int readLen = 0;
            do
            {
                readLen += reader.Read(rect.Pixels, readLen, size - readLen);
            } while (readLen < size);
        }
    }
}
